/**
 * 
 */
package logic.huffman;

import io.ImageReader;

import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.HashMap;

import javax.swing.plaf.basic.BasicInternalFrameTitlePane.MoveAction;

import datamodel.RGB;
import datamodel.huffman.tree.Tree;

/**
 * Creates Huffman code from a Huffman tree. Can encrypt and decrypt
 * BufferedImage's
 * 
 * @author Kevin Munk, Jakob Karolus
 * @version 1.0
 * 
 */
public class HuffmanCode {

	private HashMap<RGB, String> huffmanCode = null;
	private Tree huffmanTree = null;

	/**
	 * Initiates this object.
	 * 
	 * @param tree
	 *            the huffman tree to build the code from
	 */
	public HuffmanCode(Tree tree) {
		super();

		// set internal tree
		huffmanTree = tree;
		huffmanCode = new HashMap<RGB, String>();

		buildHuffmanCode();
	}

	/**
	 * Retrieves the Huffman code from the internal Huffman tree.
	 * The right subtrees will be coded with 1s and the left subtree will be coded with 0s.
	 */
	private void buildHuffmanCode() {
		//TODO: implement this method
		// Empty map. Compress() might have been called. So map needs to be re-generated.
		huffmanCode.clear();
		this.huffmanTree.moveToRoot();
		this.buildHuffmanCodeAcc("");
	}
	
	private void buildHuffmanCodeAcc(String code){
		if(!this.huffmanTree.getCurrentNode().hasRGB() && this.huffmanTree.hasLeftNode()){
			this.huffmanTree.moveToLeftNode();
			this.buildHuffmanCodeAcc(code + "0");
			this.huffmanTree.moveToParentNode();
			if(this.huffmanTree.hasRightNode()){
				this.huffmanTree.moveToRightNode();
				this.buildHuffmanCodeAcc(code + "1");
				this.huffmanTree.moveToParentNode();
			}
			return;
		} else if(this.huffmanTree.getCurrentNode().hasRGB()){
			this.huffmanCode.put(this.huffmanTree.getCurrentNode().getRGB(), code);
			return;
		}
	}

	
	/**
	 * Encrypt the image data from the ImageReader with the internal Huffman
	 * code
	 * 
	 * @param imageReader
	 *            the data which will be encrypted
	 * @return the image coded as an String. Does not contain other chars than
	 *         '0' and '1'.
	 */
	public String encryptImage(ImageReader imageReader) {
		StringBuilder result = new StringBuilder(); 
		for(RGB color : imageReader){
			
			if(color == null){
				result.setLength(0);
			}
			else if (huffmanCode.containsKey(color)){
				result.append(huffmanCode.get(color));
			}
			else{
				throw new UnsupportedOperationException("ERROR!");
			}
		}
		return result.toString();
		
	}

	/**
	 * Decrypt the encrypted image. If the encryptedImage does not contain
	 * enough colors, the remaining pixel won't be filled. If the encryptedImage
	 * has more colors than the height * width pixel specified, the remaining
	 * colors will be ignored.
	 * 
	 * @param encryptedImage
	 *            the encrypted image. the string must only contain '0' and '1'.
	 * @param width
	 *            the width of the returned image
	 * @param height
	 *            the height of the returned image
	 * @return the decrypted image
	 */
	public BufferedImage decryptImage(String encryptedImage, int width,
			int height) {
		BufferedImage image = new BufferedImage(width, height,
				BufferedImage.TYPE_INT_ARGB);
		int x = 0;
		int y = 0;
		huffmanTree.moveToRoot();
		for(int i= 0;i< encryptedImage.length();i++){
			if(encryptedImage.charAt(i) == '1'){
				if(!huffmanTree.moveToRightNode())
					throw new IllegalArgumentException("Bad String!");
			}
			else if(encryptedImage.charAt(i)== '0'){
				if(!huffmanTree.moveToLeftNode())
					throw new IllegalArgumentException("Bad String");
			}else
				throw new IllegalArgumentException("Bad String!");
			
			if(huffmanTree.getCurrentNode().hasRGB()){
				if(x == width){
					if(y == height-1) // (cannot increment) finished
						break;
					else {
						y++;
						x=0;
					}
				}
				image.setRGB(x, y, huffmanTree.getCurrentNode().getRGB().getRGBValue());
				++x;
				
				huffmanTree.moveToRoot(); // prepare to find a new color
			}
		}
		
		return image;
	}


	/**
	 * @return the huffmanCode a map which holds for every color the huffman
	 *         code
	 */
	public HashMap<RGB, String> getHuffmanCode() {
		return huffmanCode;
	}

	/**
	 * return true if the given color is in the huffman tree
	 * 
	 * @param color
	 *            the color to search
	 * @return true if tree contains color; otherwise false
	 */
	public boolean containsColor(RGB color) {
		// TODO: implement this method
		//return huffmanCode.containsKey(color);
		// Be stupid - search in tree
		huffmanTree.moveToRoot();
		return containsColorHelper(color);
	}
	
	private boolean containsColorHelper(RGB color) {
		// At leaf
		RGB rgb = huffmanTree.getCurrentNode().getRGB();
		if(rgb != null)
			if(rgb.equals(color))
				return true;
			else
				return false;
		
		if(huffmanTree.hasLeftNode()) {
			huffmanTree.moveToLeftNode();
			if(containsColorHelper(color))
				return true;
			huffmanTree.moveToParentNode();
		}
		
		if(huffmanTree.hasRightNode()) {
			huffmanTree.moveToRightNode();
			if(containsColorHelper(color))
				return true;
			huffmanTree.moveToParentNode();
		}
		
		
		return false;
}

	/**
	 * severs the given amount of levels in the stored tree to compress image
	 * information<br>
	 * if the amount is bigger than the actual number of levels nothing is done
	 * 
	 * @param count
	 *            the amount of levels to sever
	 * @return true if severing was successful; otherwise false (count >= number
	 *         of levels)
	 */
	public boolean compress(int count) {
		if (count >=huffmanTree.getTreeHeight()){
			return false;
		}
		ArrayList<RGB> cutOff = new ArrayList<RGB>();
		for(int i =0;i< count;i++)
			cutOff.addAll(huffmanTree.removeLowestTreeLevel());
		
		
		for(RGB r : cutOff) {
			StringBuilder rEnc = new StringBuilder();
			rEnc.append(huffmanCode.get(r));
			rEnc.deleteCharAt(rEnc.length()-1);
			huffmanCode.put(r, rEnc.toString());
		}
		
		
		return true;
	}
	

	/**
	 * @return the huffmanTree
	 */
	public Tree getHuffmanTree() {
		return huffmanTree;
	}

}
